﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MongoDB.Bson;
using MongoDB.Driver;

namespace MongoScienceDAL.Core.Tests
{
    [TestClass]
    public class ScienceEntrySerialiserTests
    {
        #region Test Setup and Id / CollectionName

        public const string CollectionName = "testCollection";

        public ScienceRepository GetRepository()
        {
            TestHarness.ResetDatabase();

            var scienceCollection = new ScienceCollection
            {
                Index = 1,
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test collection",
                Name = CollectionName
            };

            scienceCollection = TestHarness.DataSource.AddCollection(scienceCollection);

            //Add some types
            var testType1 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t1",
                Name = "test1",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Text
            };

            var testType2 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t2",
                Name = "test2",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.DateTime
            };

            var testType3 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t3",
                Name = "test3",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Value
            };

            var testType4 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t4",
                Name = "test4",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ValueSet
            };

            var testType5 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t5",
                Name = "test5",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Files
            };

            var testType6 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t6",
                Name = "test6",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.VersionedFile
            };

            var testType7 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t7",
                Name = "test7",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ParentLink
            };

            var testType8 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t8",
                Name = "test8",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.ChildLinks
            };

            var testType9 = new ScienceType
            {
                CreatedBy = "testuser",
                CreatedOn = new DateTime(1985, 5, 23),
                Description = "test description",
                Hidden = false,
                Index = 1,
                Key = "t9",
                Name = "test9",
                Required = false,
                Settings = new Dictionary<string, string>(),
                Type = ScienceTypeEnum.Boolean
            };

            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType1);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType2);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType3);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType4);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType5);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType6);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType7);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType8);
            TestHarness.DataSource.AddCollectionType(scienceCollection.Name, testType9);

            return new ScienceRepository(TestHarness.DataSource, CollectionName);
        }
        
        [TestMethod]
        public void SerialiserCreatesObjectIdAndCollectionName()
        {
            var repo = GetRepository();

            var objectId = repo.Create(null);

            var mongodb = TestHarness.DataSource.Database;

            var collection = mongodb.GetCollection(CollectionName);

            var result = collection.FindOneById(objectId);

            Assert.IsNotNull(result); //id exists

            Assert.AreEqual(result["_col"], CollectionName);
            Assert.AreEqual(result["_id"], objectId);
        }

        [TestMethod]
        public void DeserialiseCreatesObjectIdAndCollection()
        {
            var repo = GetRepository();

            //create test object
            var objectId = repo.Create(null);

            //deserailise
            var scienceEntry = repo.Retrieve(objectId);

            Assert.IsNotNull(scienceEntry);

            Assert.AreEqual(scienceEntry.Id, objectId);
            Assert.AreEqual(scienceEntry.Collection.Name, CollectionName);
            Assert.IsTrue(scienceEntry.Data.Count == 0);
        }

        #endregion

        #region Types Tests

        [TestMethod]
        public void SerialiserHandlesTextData()
        {
            //create new entry in the database
            var repo = GetRepository();

            const string testValue = "this is a test string";
            const string key = "t1";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key], testValue);

            //verify that deserialisation works
            var scienceEntry = repo.Retrieve(objectId);

            Assert.IsNotNull(scienceEntry);
            Assert.AreEqual(scienceEntry.Id, objectId);
            Assert.IsTrue(scienceEntry.Data.ContainsKey(key));
            Assert.AreEqual(scienceEntry.Data[key], testValue);
        }

        [TestMethod]
        public void SerialiserHandlesDateTimeData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = DateTime.Now;
            const string key = "t2";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsDateTime.Year, testValue.Year);

            //verify that deserialisation works
            var scienceEntry = repo.Retrieve(objectId);

            Assert.IsNotNull(scienceEntry);
            Assert.AreEqual(scienceEntry.Id, objectId);
            Assert.IsTrue(scienceEntry.Data.ContainsKey(key));
            Assert.AreEqual(((DateTime)scienceEntry.Data[key]).Day, testValue.Day);
        }

        [TestMethod]
        public void SerialiserHandlesScienceQuantityData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = 1500d;
            const string key = "t3";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsDouble, testValue);

            //verify that deserialisation works
            var value = repo.Retrieve(objectId);

            Assert.IsNotNull(value);
            Assert.AreEqual(value.Id, objectId);
            Assert.IsTrue(value.Data.ContainsKey(key));
            var quantity = (double)value.Data[key];
            Assert.IsNotNull(quantity);
            Assert.AreEqual(quantity, testValue);
        }

        [TestMethod]
        public void SerialiserHandlesScienceQuantitySetData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = new List<double> {1200,500};
            const string key = "t4";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsBsonArray[0], testValue[0]);

            //verify that deserialisation works
            var values = repo.Retrieve(objectId);

            Assert.IsNotNull(values);
            Assert.AreEqual(values.Id, objectId);
            Assert.IsTrue(values.Data.ContainsKey(key));
            var expected = (List<double>) values.Data[key];
            Assert.IsNotNull(expected);
            Assert.AreEqual(expected[1], testValue[1]);
        }

        [TestMethod]
        public void SerialiserHandlesFilesData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = new List<ScienceFile>
                                {
                                    new ScienceFile
                                        {
                                            Filename = "testName",
                                            FileId = ObjectId.GenerateNewId(),
                                            Size = 1000,
                                            UploadedBy = "testuser",
                                            UploadedOn = DateTime.Today
                                        }
                                };
            const string key = "t5";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["fn"].AsString, testValue[0].Filename);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["fid"].AsObjectId, testValue[0].FileId);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["ub"].AsString, testValue[0].UploadedBy);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["uo"].AsDateTime.Year, testValue[0].UploadedOn.Year);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["s"].AsInt64, testValue[0].Size);


            //verify that deserialisation works
            var scienceEntry = repo.Retrieve(objectId);

            Assert.IsNotNull(scienceEntry);
            Assert.AreEqual(scienceEntry.Id, objectId);
            Assert.IsTrue(scienceEntry.Data.ContainsKey(key));

            var scienceFileList = scienceEntry.Data[key] as List<ScienceFile>;

            Assert.IsNotNull(scienceFileList);
            Assert.IsTrue(scienceFileList.Count == 1);
            Assert.AreEqual(scienceFileList[0].Filename, testValue[0].Filename);
            Assert.AreEqual(scienceFileList[0].FileId, testValue[0].FileId);
            Assert.AreEqual(scienceFileList[0].UploadedBy, testValue[0].UploadedBy);
            Assert.AreEqual(scienceFileList[0].UploadedOn.Year, testValue[0].UploadedOn.Year);
            Assert.AreEqual(scienceFileList[0].Size, testValue[0].Size);
        }

        [TestMethod]
        public void SerialiserHandlesVersionedFileData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = new List<VersionedFile>
                                {
                                    new VersionedFile
                                        {
                                            Filename = "testName",
                                            FileId = ObjectId.GenerateNewId(),
                                            Size = 1000,
                                            UploadedBy = "testuser",
                                            UploadedOn = DateTime.Today,
                                            Version = "1.0"
                                        }
                                };
            const string key = "t6";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["fn"].AsString, testValue[0].Filename);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["fid"].AsObjectId, testValue[0].FileId);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["ub"].AsString, testValue[0].UploadedBy);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["uo"].AsDateTime.Year, testValue[0].UploadedOn.Year);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["s"].AsInt64, testValue[0].Size);
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsBsonDocument["v"].AsString, testValue[0].Version);

            //verify that deserialisation works
            var scienceEntry = repo.Retrieve(objectId);

            Assert.IsNotNull(scienceEntry);
            Assert.AreEqual(scienceEntry.Id, objectId);
            Assert.IsTrue(scienceEntry.Data.ContainsKey(key));

            var scienceFileList = scienceEntry.Data[key] as List<VersionedFile>;

            Assert.IsNotNull(scienceFileList);
            Assert.IsTrue(scienceFileList.Count == 1);
            Assert.AreEqual(scienceFileList[0].Filename, testValue[0].Filename);
            Assert.AreEqual(scienceFileList[0].FileId, testValue[0].FileId);
            Assert.AreEqual(scienceFileList[0].UploadedBy, testValue[0].UploadedBy);
            Assert.AreEqual(scienceFileList[0].UploadedOn.Year, testValue[0].UploadedOn.Year);
            Assert.AreEqual(scienceFileList[0].Size, testValue[0].Size);
            Assert.AreEqual(scienceFileList[0].Version, testValue[0].Version);
        }

        [TestMethod]
        public void SerialiserHandlesParentLinkData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = ObjectId.GenerateNewId();
            const string key = "t7";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsObjectId, testValue);

            //verify that deserialisation works
            var values = repo.Retrieve(objectId);

            Assert.IsNotNull(values);
            Assert.AreEqual(values.Id, objectId);
            Assert.IsTrue(values.Data.ContainsKey(key));
            var expected = (ObjectId)values.Data[key];
            Assert.IsNotNull(expected);
            Assert.AreEqual(expected, testValue);
        }

        [TestMethod]
        public void SerialiserHandlesChildLinksData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = new List<ObjectId> { ObjectId.GenerateNewId(), ObjectId.GenerateNewId() };
            const string key = "t8";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsBsonArray[0].AsObjectId, testValue[0]);
            Assert.AreEqual(rawItem[key].AsBsonArray[1].AsObjectId, testValue[1]);

            //verify that deserialisation works
            var values = repo.Retrieve(objectId);

            Assert.IsNotNull(values);
            Assert.AreEqual(values.Id, objectId);
            Assert.IsTrue(values.Data.ContainsKey(key));
            var expected = (List<ObjectId>)values.Data[key];
            Assert.IsNotNull(expected);
            Assert.AreEqual(expected[0], testValue[0]);
            Assert.AreEqual(expected[1], testValue[1]);
        }

        [TestMethod]
        public void SerialiserHandlesBooleanData()
        {
            //create new entry in the database
            var repo = GetRepository();

            var testValue = false;
            const string key = "t9";

            var data = new Dictionary<string, object> { { key, testValue } };

            var objectId = repo.Create(data);

            //verify it is stored in the database
            var mongodb = TestHarness.DataSource.Database;

            var rawItem = mongodb.GetCollection(CollectionName).FindOneById(objectId);

            Assert.IsNotNull(rawItem); //id exists
            Assert.IsTrue(rawItem.Contains(key));
            Assert.AreEqual(rawItem[key].AsBoolean, testValue);

            //verify that deserialisation works
            var values = repo.Retrieve(objectId);

            Assert.IsNotNull(values);
            Assert.AreEqual(values.Id, objectId);
            Assert.IsTrue(values.Data.ContainsKey(key));
            var expected = (bool)values.Data[key];
            Assert.IsNotNull(expected);
            Assert.AreEqual(expected, testValue);
        }

        #endregion

    }
}
